package com.spring.springZookeeper.common;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author hw
 * <p>
 * zookeeper 的 curator-recipes 插件使用
 * </p>
 */
@Component
public class ZkUtil {
private static final Logger log = LoggerFactory.getLogger(ZkUtil.class);
    
    //集群节点
    public static final String zkServerClusterConnect = "127.0.0.1:2181,127.0.0.1:2182";
    
    //单一节点
    public static final String zkServerSingleConnect = "127.0.0.1:2181";
    

/*    
 *  CuratorFramework client = client();
    log.info("客户端状态：{}", client.getState());
    
    client.start();
    log.info("客户端状态：{}", client.getState());
    
    
    //创建节点
    //create(client, "/father/me", "me");
    
    //查询节点
    //query(client, "/father/me");
    
    //修改节点的值
    //update(client, "/father/me", "newMe");
    
    //删除节点
    //delete(client, "/father/me");
    
    client.close();
    log.info("客户端状态：{}", client.getState());
    
    */
    
    /**
     * 描述：获取CuratorFramework的客户端
     * @return
     */
    public static CuratorFramework client(){
        log.info("准备创建CuratorFramework客户端");
        int sessionTimeoutMs = 10000;//会话超时时间
        int connectionTimeoutMs = 3000;//初次链接超时时间
        int n = 3;//重试链接次数
        int sleepMsBetweenRetries = 3000;//每次重试连接间隔毫秒数
        
        //RetryPolicy重试显现策略有很多，具体可以查看RetryPolicy的每个实现类进行测试。
        RetryPolicy retryPolicy = new RetryNTimes(n, sleepMsBetweenRetries);
        
        //CuratorFrameworkFactory.newClient是创建客户端的一种方法
        //CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(zkServerClusterConnect, sessionTimeoutMs, connectionTimeoutMs, retryPolicy);
        
        //创建客户端方法还有 CuratorFrameworkFactory.builder()....
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().connectString(zkServerClusterConnect).connectionTimeoutMs(connectionTimeoutMs).sessionTimeoutMs(sessionTimeoutMs).retryPolicy(retryPolicy).namespace("myspace").build();
        log.info("完成创建CuratorFramework客户端");
        curatorFramework.start();
        log.info("连接启动完成");
        return curatorFramework;
    }
    
    /**
     * 描述：递归创建节点数据
     * @param client
     * @param path
     * @param data
     * @throws Exception
     */
    public static void create(CuratorFramework client, String path, String data) throws Exception{
        log.info("准备递归创建节点，path：{}，data：{}", path, data);
        client.create().creatingParentsIfNeeded()//如果path父节点不存在，一并创建
        .withMode(CreateMode.PERSISTENT)//永久节点
        .withACL(Ids.OPEN_ACL_UNSAFE)//所有可见权限
        .forPath(path, data.getBytes());//设置节点数据
        log.info("完成递归创建节点，path：{}，data：{}", path, data);
    }
    
    /**
     * 描述：查询节点值
     * @param client
     * @param path
     * @return
     * @throws Exception
     */
    public static String query(CuratorFramework client, String path) throws Exception{
        log.info("准备查询节点：{} 值",path);
        String data = new String(client.getData().storingStatIn(getStat(client, path)).forPath(path));
        log.info("完成查询节点：{}， 值：{}", path, data);
        return data;
    }
    
    /**
     * 描述：修改节点的值
     * @param client
     * @param path
     * @param data
     * @return
     * @throws Exception
     */
    public static Stat update(CuratorFramework client, String path, String data) throws Exception{
        log.info("准备修改节点：{}，值：{}", path, data);
        Stat stat = client.setData().withVersion(getStat(client, path).getVersion()).forPath(path, data.getBytes());
        log.info("完成修改节点：{}，值：{}", path, data);
        return stat;
    }
    
    /**
     * 描述：淡出节点
     * @param client
     * @param path
     * @throws Exception
     */
    public static void delete(CuratorFramework client, String path) throws Exception{
        log.info("准备删除节点：{}",path);
        client.delete().guaranteed()//就算网络遇见抖动，只要连接成功，也会保证删除
            .deletingChildrenIfNeeded()//递归删除子节点
            .withVersion(getStat(client, path).getVersion()).forPath(path);
        log.info("完成删除节点：{}",path);
    }
    
    /**
     * 描述：查询节点元数据，查询、修改、删除时需要该数据的信息作为参数
     *        同样也是判断节点是否存在的方法
     * @param client
     * @param path
     * @return
     * @throws Exception
     */
    public static Stat getStat(CuratorFramework client, String path) throws Exception{
        log.info("准备获取 节点：{}的stat",path);
        Stat stat = client.checkExists().forPath(path);
        if(null==stat){
            log.info("获取节点：{} 不存在或已删除",path);
        }else{
            log.info("完成获取节点：{} stat信息, version：{}",path,stat.getVersion());
        }
        return stat;
    }
    
    /**
     * 描述：测试客户端的三个状态
     * client.isStarted();//官方已经建议使用该方法，后续升级可能不会对其维护，请不要再使用。
     */
    public static void status(){
        CuratorFramework client = client();
        //client.isStarted();//官方已经建议使用该方法，后续升级可能不会对其维护，请不要再使用。
        log.info("客户端状态：{}", client.getState());
        client.start();
        log.info("客户端状态：{}", client.getState());
//        client.close();
//        log.info("客户端状态：{}", client.getState());
    }
}
